package ru.volgogradetzzz.tween {
	import easing.Linear;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.utils.getTimer;
	import ru.volgogradetzzz.utils.Pool;
	
	public class EnterFrameTween {
		private const _ENTRIES:Array = new Array();
		private const _PAUSED_ENTRIES:Array = new Array();
		private const _DUMMY:Shape = new Shape();
		private const _POOL:Pool = new Pool();
		
		private var _dt:Number;
		
		public function addTween(obj:Object, propName:String, valueStart:Number, valueEnd:Number, duration:uint, useFrames:Boolean = false, completeCallback:Function = null, updateCallback:Function = null, easingFunction:Function = null):void {
			var entry:TweenEntry = _POOL.getObject(TweenEntry);
			entry.init(obj, propName, valueStart, valueEnd, duration, useFrames, completeCallback, updateCallback, easingFunction || Linear.easeOut);
			_ENTRIES.push(entry);
			
			_dt = getTimer();
			_DUMMY.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		public function stopTween(obj:Object, propName:String):void {
			var entry:TweenEntry;
			
			for (var entryIndex:int = 0, numEntries:uint = _ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _ENTRIES[entryIndex];
				if (entry.obj == obj && entry.propName == propName) {
					removeEntry(entryIndex);
					poolEntry(entry);
					
					break;
				}
			}
		}
		
		public function pauseTween(obj:Object, propName:String):void {
			var entry:TweenEntry;
			var ind:int;
			
			for (var entryIndex:int = 0, numEntries:uint = _ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _ENTRIES[entryIndex];
				if (entry.obj == obj && entry.propName == propName) {
					removeEntry(entryIndex);
					
					_PAUSED_ENTRIES.push(entry);
					
					break;
				}
			}
		}
		
		public function resumeTween(obj:Object, propName:String):void {
			var entry:TweenEntry;
			var ind:int;
			
			for (var entryIndex:int = 0, numEntries:uint = _PAUSED_ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _PAUSED_ENTRIES[entryIndex];
				if (entry.obj == obj && entry.propName == propName) {
					_PAUSED_ENTRIES.splice(entryIndex, 1);
					
					_ENTRIES.push(entry);
					
					_dt = getTimer();
					_DUMMY.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
					
					break;
				}
			}
		}
		
		public function pauseAllTweens():void {
			var entry:TweenEntry;
			
			for (var entryIndex:int = 0, numEntries:uint = _ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _ENTRIES[entryIndex];
				_PAUSED_ENTRIES.push(entry);
			}
			
			_ENTRIES.length = 0;
			_DUMMY.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		public function resumeAllTweens():void {
			var entry:TweenEntry;
			
			for (var entryIndex:int = 0, numEntries:uint = _PAUSED_ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _PAUSED_ENTRIES[entryIndex];
				_ENTRIES.push(entry);
			}
			
			_PAUSED_ENTRIES.length = 0;
			
			_dt = getTimer();
			_DUMMY.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		public function stopAllTweens():void {
			var entry:TweenEntry;
			
			for (var entryIndex:int = 0, numEntries:uint = _ENTRIES.length; entryIndex < numEntries; entryIndex++) {
				entry = _ENTRIES[entryIndex];
				poolEntry(entry);
			}
			
			_ENTRIES.length = 0;
			_DUMMY.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		private function enterFrameHandler(event:Event):void {
			var entry:TweenEntry;
			
			var t:int = getTimer();
			_dt = t - _dt;
			
			for (var entryIndex:int = _ENTRIES.length - 1; entryIndex >= 0; entryIndex--) {
				entry = _ENTRIES[entryIndex];
				
				if (entry.useFrames) {
					entry.currTime++;
				}else{
					entry.currTime += _dt;
				}
				
				if (entry.currTime >= entry.duration) {
					entry.obj[entry.propName] = entry.valueEnd;
					
					if (entry.completeCallback != null) {
						entry.completeCallback(entry.obj);
					}
					
					removeEntry(entryIndex);
					poolEntry(entry);
				}else {
					entry.obj[entry.propName] = entry.easingFunction(entry.currTime, entry.valueStart, entry.valueEnd - entry.valueStart, entry.duration);
					
					if (entry.updateCallback != null) {
						entry.updateCallback(entry.obj);
					}
				}
			}
			
			_dt = t;
		}
		
		private function removeEntry(index:uint):void {
			_ENTRIES.splice(index, 1);
			
			if (_ENTRIES.length == 0) {
				_DUMMY.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
			}
		}
		
		private function poolEntry(entry:TweenEntry):void {
			entry.clear();
			_POOL.poolObject(entry);
		}
	}
}

internal class TweenEntry
{
	public var obj:Object;
	public var propName:String;
	public var valueStart:Number;
	public var valueEnd:Number;
	public var duration:Number;
	public var useFrames:Boolean;
	public var completeCallback:Function;
	public var updateCallback:Function;
	public var easingFunction:Function;
	
	public var currTime:uint;
	
	public function init(obj:Object, propName:String, valueStart:Number, valueEnd:Number, duration:uint, useFrames:Boolean, completeCallback:Function, updateCallback:Function, easingFunction:Function):void
	{
		this.obj = obj;
		this.propName = propName;
		this.valueStart = valueStart;
		this.valueEnd = valueEnd;
		this.duration = duration;
		this.useFrames = useFrames;
		this.completeCallback = completeCallback;
		this.updateCallback = updateCallback;
		this.easingFunction = easingFunction;
		
		currTime = 0;
	}
	
	public function clear():void
	{
		obj = null;
		propName = null;
		completeCallback = null;
		updateCallback = null;
		easingFunction = null;
	}
}